package com.buyman.server.dao;

import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Expression;
import org.hibernate.criterion.MatchMode;
import org.hibernate.metadata.ClassMetadata;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.buyman.server.util.PaginatedList;

/**
 * 
 * @author Lionel Sicilia (Aplay America S.A.)
 * @version Oct 10, 2008
 */
public class GenericHBDAO<T extends Serializable, PK extends Serializable> extends HibernateDaoSupport implements IGenericDAO<T, PK> {

	private static Log log = LogFactory.getLog(GenericHBDAO.class);

	private Class returnedClass;

	public GenericHBDAO() {
		
	}

	public GenericHBDAO(String returnedClass) {
		try {
			this.returnedClass = Class.forName(returnedClass);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

	public GenericHBDAO(Class returnedClass) {
		this.returnedClass = returnedClass;
		// this.returnedClass = ReflectionUtils.getTypeArguments(GenericHBDAO.class, getClass()).get(0);
	}

	public Class returnedClass() {
		return this.returnedClass;
	}

	public String className() {
		return this.returnedClass().getName();
	}

	public void delete(T obj) {
//		log.debug("Objeto de tipo " + className() + " eliminando");
		getHibernateTemplate().delete(obj);
	}

	/**
	 * Apply a filter to a persistent collection. A filter is a Hibernate query that may refer to this, the collection element. Filters allow efficient access to very large lazy collections.
	 * (Executing the filter does not initialize the collection.)
	 * 
	 * @param collection
	 *            a persistent collection to filter
	 * @param filter
	 *            a filter query string
	 * @return Collection the resulting collection
	 * 
	 * @see Session.createFilter()
	 */
	public Collection filter(Collection<T> collection, String filter) {
		return (Collection) getHibernateTemplate().execute(getFilterCallback(collection, filter));
	}

	public List findAll() {
//		log.debug("Obteniendo todos los objetos de tipo " + className());
		return getHibernateTemplate().find("from " + className());
	}

	public List findAll(String orderBy) {
//		log.debug("Obteniendo todos los objetos de tipo " + className() + " ordenados por " + orderBy);
		String query = "from " + className();
		if (orderBy.trim().length() > 0) {
			query += " order by " + orderBy;
		}
		return getHibernateTemplate().find(query);
	}

	public List findAll(Integer firstElement, Integer maxElements) {
//		log.debug("Obteniendo los objetos de tipo " + className() + " paginado desde " + firstElement + ". Traigo hasta " + maxElements
//				+ " elementos");
		DetachedCriteria criteria = DetachedCriteria.forClass(returnedClass());
		return getHibernateTemplate().executeFind(getFindByCriteriaPaginatedCallback(criteria, firstElement, maxElements));
	}

	public List findByExample(T obj, Integer firstElement, Integer maxElements) {
		DetachedCriteria criteria = DetachedCriteria.forClass(returnedClass());
		criteria.add(Example.create(obj).ignoreCase().enableLike(MatchMode.ANYWHERE));
		return getHibernateTemplate().executeFind(getFindByCriteriaPaginatedCallback(criteria, firstElement, maxElements));
	}

	public List findByExample(T obj) {
//		log.debug("Obteniendo todos los Objetos de tipo " + className() + " By Example");
		DetachedCriteria criteria = DetachedCriteria.forClass(returnedClass());
		criteria.add(Example.create(obj).ignoreCase().enableLike(MatchMode.ANYWHERE));
		return getHibernateTemplate().findByCriteria(criteria);
	}

	public List findById(Collection<PK> ids) {
		DetachedCriteria criteria = DetachedCriteria.forClass(returnedClass());
		ClassMetadata classMetadata = getSessionFactory().getClassMetadata(returnedClass());
		String idPropertyName = classMetadata.getIdentifierPropertyName();
		criteria.add(Expression.in(idPropertyName, ids));
		return getHibernateTemplate().findByCriteria(criteria);
	}

	@SuppressWarnings("unchecked")
	public T findById(PK id) {
//		log.debug("obteniendo objeto de tipo " + className() + " by Id: " + id);
		return (T) getHibernateTemplate().get(returnedClass(), id);
	}

	public List findByProperty(String propertyName, Object value, Integer firstElement, Integer maxElements) {
		DetachedCriteria criteria = DetachedCriteria.forClass(returnedClass());
		criteria.add(Expression.like(propertyName, value));
		return getHibernateTemplate().executeFind(getFindByCriteriaPaginatedCallback(criteria, firstElement, maxElements));
	}

	public List findByProperty(String propertyName, Object value) {
		DetachedCriteria criteria = DetachedCriteria.forClass(returnedClass());
		criteria.add(Expression.like(propertyName, value));
		return getHibernateTemplate().findByCriteria(criteria);
	}

	public Object findOneByProperty(String propertyName, Object value) {
		Object obj = null;
		List results = this.findByProperty(propertyName, value);
		if (results != null && results.size() > 0) {
			obj = results.get(0);
		}
		return obj;
	}

	public List findByPropertyContent(String propertyName, String value, Integer firstElement, Integer maxElements) {
		DetachedCriteria criteria = DetachedCriteria.forClass(returnedClass());
		criteria.add(Expression.ilike(propertyName, value, MatchMode.ANYWHERE));
		return getHibernateTemplate().executeFind(getFindByCriteriaPaginatedCallback(criteria, firstElement, maxElements));
	}

	public List findByPropertyContent(String propertyName, String value) {
		DetachedCriteria criteria = DetachedCriteria.forClass(returnedClass());
		criteria.add(Expression.ilike(propertyName, value, MatchMode.ANYWHERE));
		return getHibernateTemplate().findByCriteria(criteria);
	}

	public void persist(T obj) {
//		log.debug("Persistiendo objeto de tipo " + className());
		try
		{
			getHibernateTemplate().saveOrUpdate(obj);
		}
		catch(Exception ex)	// INTENTAMOS CON UN MERGE POR SI LA EXCEPCION SE PRODUJO POR UN NONUNIQUEOBJECTEXCEPTION
		{
			getHibernateTemplate().merge(obj);
		}
	}

	public void refresh(T obj) {
		getHibernateTemplate().refresh(obj);
	}

	private HibernateCallback getFindByCriteriaPaginatedCallback(final DetachedCriteria criteria, final Integer firstElement, final Integer count) {
		return new HibernateCallback() {

			public Object doInHibernate(Session session) throws HibernateException {
				Criteria criteriaExec = criteria.getExecutableCriteria(session);
				int size = criteriaExec.list().size();
				List list = criteriaExec.setFirstResult(firstElement).setMaxResults(count).list();
				return new PaginatedList(list, firstElement, count, size);
			}
		};
	}

	/**
	 * <p>
	 * Get a new HibernateCallback for filtering collections.
	 * </p>
	 * <p>
	 * Apply a filter to a persistent collection. A filter is a Hibernate query that may refer to this, the collection element. Filters allow efficient access to very large lazy collections.
	 * (Executing the filter does not initialize the collection.)
	 * </p>
	 * 
	 * @param collection
	 *            a persistent collection to filter
	 * @param filter
	 *            a filter query string
	 * @return Collection the resulting collection
	 */
	private static HibernateCallback getFilterCallback(final Collection collection, final String filter) {
		return new HibernateCallback() {

			public Object doInHibernate(Session session) throws HibernateException {
				return session.createFilter(collection, filter).list();
			}
		};
	}

	public void updateBySqlCacheable(String query) {
		// System.err.println(getSession().createSQLQuery(query).e getQueryString());//executeUpdate();
	}

	public List findAllByHQL(String query, boolean cacheable) {
		Query q=getSession().createQuery(query);
		return q.list();
	}
	
	public List findAllByHQL(String query, String param1, Object object1, boolean cacheable) {
		Query q=getSession().createQuery(query);
		q.setParameter(param1, object1);
		return q.list();
	}
	
	public List findAllByHQL(String query, String[] params, Object[] valoresParams, boolean cacheable)
	{
		Query q = getSession().createQuery( query );
		int i = 0;
		try
		{
			for( i = valoresParams.length - 1; i >= 0; i-- )
			{
				q.setParameter(params[ i ], valoresParams[ i ]);
			}
		}
		catch(IndexOutOfBoundsException exc)
		{		}
		return q.list();
	}
	
	public List findAllMaxByHQL(String query, Integer max, Integer desde) {
		
		Query q=getSession().createQuery(query);
		if (max != null) {
			q.setMaxResults(max);
		}
		if (desde != null) {
			q.setFirstResult(desde);
		}
		return q.list();
	}
	
	public List findAllMaxByHQLFilterByList(String query, Integer max, Integer desde, List l) {
		
		Query q=getSession().createQuery(query);
		if (max != null) {
			q.setMaxResults(max);
		}
		if (desde != null) {
			q.setFirstResult(desde);
		}
		q.setParameterList("list", l);
		return q.list();
	}

	public Object findOneByHQL(String query) {
		return getSession().createQuery(query).uniqueResult();
	}
	
	public Object findOneByHQLFilterByList(String query, List l) {
		Query q=getSession().createQuery(query);
		q.setParameterList("list", l);
		return q.uniqueResult();
	}
	
	
	public Object findOneByHQLEvict(String query) {
		Session session = getSession();
		Object object = session.createQuery(query).uniqueResult();
		if (object != null) {
			//session.evict(object);
		}
		return object;
		
	}

	public List findAllMaxByHQL(String query, Integer max, Integer cursorDesde, String param1, Object object1) {
		Query query2 = getSession().createQuery(query);
		query2.setParameter(param1, object1);
		query2.setMaxResults(max);
		query2.setFirstResult(cursorDesde);
		return query2.list();
	}
	
	public List findAllMaxByHQL(String query, Integer max, Integer cursorDesde, String[] params, Object[] valoresParams)
	{
		Query query2 = getSession().createQuery(query);

		int i = 0;
		try
		{
			for( i = valoresParams.length - 1; i >= 0; i-- )
			{
				query2.setParameter(params[i], valoresParams[i]);
			}
		}
		catch( IndexOutOfBoundsException exc )
		{
		}
		query2.setMaxResults(max);
		query2.setFirstResult(cursorDesde);

		return query2.list();
	}
	
	public Session getSessionHB() {
		return getSession();
	}
	
	public Date getFechaBaseDatos()
	{
		SQLQuery query = getSession().createSQLQuery( "SELECT CURRENT_TIMESTAMP" );
		Date fecha = (Date) query.uniqueResult();
		return fecha;
	}
}